/**
 * @file        application.cpp
 * @brief       Defines the GTD application barebones.
 *
 * @author      Emre Turkay <emreturkay at gmail>
 * @date        Jun.04.2008
 */

#include "application.hpp"
#include "interface.hpp"
#include "gtd.hpp"

application::application(interface& intf, gtd::gtd& backend)
    : quit_(false),
      intf_(intf),
      backend_(backend)
{
    intf_.command("help", boost::bind(&application::help, this, _1),
                  "Show this help screen.");
    intf_.command("next", boost::bind(&application::next, this, _1),
                  "Show next action.");
    intf_.command("done", boost::bind(&application::done, this, _1),
                  "Mark the current action as done.");
    intf_.command("cancel", boost::bind(&application::cancel, this, _1),
                  "Cancel the current action.");
    intf_.command("defer", boost::bind(&application::defer, this, _1),
                  "Defer the current action.");
    intf_.command("append", boost::bind(&application::append, this, _1),
                  "Append item given in quotes.");
    intf_.command("prepend", boost::bind(&application::prepend, this, _1),
                  "Prepend item given in quotes.");
    intf_.command("context", boost::bind(&application::context, this, _1),
                  "Set/remove/list contexts. context @ctx, context -, context");
    intf_.command("stat", boost::bind(&application::stat, this, _1),
                  "Give the statistics.");
    intf_.command("list", boost::bind(&application::list, this, _1),
                  "Lists the projects. If a project given lists the items.");
    intf_.command("quit", boost::bind(&application::quit, this, _1),
                  "Leave the gtdshell.");
}

void application::run()
{
    prompt();

    while (!quit_)
        if (!intf_.process())
            quit_ = true;
}

void application::help(const interface::args_type&)
{
    intf_.help();
}

void application::next(const interface::args_type&)
{
    prompt();
}

void application::done(const interface::args_type&)
{
    if (pid_)
    {
        backend_.done(*pid_);
        intf_.write("done");
        prompt();
    }
}

void application::cancel(const interface::args_type&)
{
    if (pid_)
    {
        backend_.cancel(*pid_);
        intf_.write("cancelled");
        prompt();
    }
}

void application::defer(const interface::args_type&)
{
    if (pid_)
    {
        backend_.defer(*pid_);
        intf_.write("deferred");
        prompt();
    }
}

void application::list(const interface::args_type& args)
{
    if (args.size() == 0)
    {
        bool project_exists = false;
        size_t item_count = 0;
        size_t anonymous = 0;
        std::list<gtd::gtd::project_id> ids = backend_.project_ids();

        for (std::list<gtd::gtd::project_id>::const_iterator i = ids.begin();
             i != ids.end(); ++i)
        {
            const boost::optional<std::string>& name =
                backend_.project_name(*i);
            size_t items = backend_.item_count(*i);
            if (name)
            {
                std::ostringstream os;
                os << *name << " (" << items << ')';
                intf_.write(os.str());
            }
            else
                ++anonymous;
            project_exists = true;
            item_count += items;
        }

        if (anonymous)
        {
            std::ostringstream os;
            os << "--anonymous-- (" << anonymous << ")";
            intf_.write(os.str());
        }

        if (!project_exists)
            intf_.write("There is no project.");
        else
        {
            std::ostringstream os;
            os << item_count << " total items.";
            intf_.write(os.str());
        }
    }
    else if ((args.size() == 1) && (args[0][0] == '+'))
    {
        std::string project_name(args[0].begin() + 1, args[0].end());
        boost::optional<gtd::gtd::project_id> id =
            backend_.find_project(project_name);
        if (!id)
            intf_.write("There is no such project.");
        else
        {
            std::list<size_t> l = backend_.items(*id);
            size_t n = 1;
            for (std::list<size_t>::const_iterator i = l.begin();
                 i != l.end(); ++i, ++n)
            {
                const gtd::action& action = backend_.item(*id, *i);
                std::ostringstream os;
                os << '[' << n << "] " << action.description();
                intf_.write(os.str());
            }
        }
    }
    else
        intf_.write("Usage: list +project_name");
}

void application::quit(const interface::args_type&)
{
    quit_ = true;
    intf_.write("bye.");
}

void application::append(const interface::args_type& args)
{
    if (args.size() != 1)
    {
        intf_.write("Usage: append value");
    }
    else
    {
        backend_.append(args[0]);
        intf_.write("Added.");
    }
}

void application::prepend(const interface::args_type& args)
{
    if (args.size() != 1)
    {
        intf_.write("Usage: prepend value");
    }
    else
    {
        backend_.prepend(args[0]);
        intf_.write("Added.");
    }
}

void application::context(const std::vector<std::string>& args)
{
    if (args.empty())
    {
        if (context_.size())
        {
            std::ostringstream os;
            os << "Context: " << context_;
            intf_.write(os.str());
            prompt();
        }
        else
            intf_.write("There is no context defined.");
    }
    else if ((args.size() == 1) && (args[0] == "-"))
    {
        context_ = "";
        intf_.write("Removed the context.");
        prompt();
    }
    else if ((args.size() == 1) && (args[0][0] == '@'))
    {
        context_ = std::string(args[0].begin() + 1, args[0].end());
        std::ostringstream os;
        os << "Context is set to: " << context_;
        intf_.write(os.str());
        prompt();
    }
    else
        intf_.write("Invalid command.");
}

void application::stat(const std::vector<std::string>&)
{
    std::set<gtd::done_item> l = backend_.done_items(boost::gregorian::days(7));
    std::map<boost::gregorian::date, size_t> m;

    boost::gregorian::day_iterator i(boost::gregorian::day_clock::local_day()
                                     - boost::gregorian::days(6));

    for (; i <= boost::gregorian::day_clock::local_day(); ++i)
        m[*i] = 0;

    for (std::set<gtd::done_item>::const_iterator i = l.begin();
         i != l.end(); ++i)
        m[i->time().date()] += 1;

    for (std::map<boost::gregorian::date, size_t>::const_iterator i = m.begin();
         i != m.end(); ++i)
    {
        std::ostringstream os;
        os << i->first.day_of_week() << '\t' << i->second;
        intf_.write(os.str());
    }
}

void application::prompt()
{
    pid_ = boost::none;

    std::list<gtd::gtd::project_id> l;
    
    if (context_.size())
        l = backend_.project_ids(context_);
    else
        l = backend_.project_ids();

    std::ostringstream os;

    if (!l.empty())
    {
        const gtd::project& p = backend_.get_project(l.front());
        const gtd::action& a = p.item(p.top());

        os << "next: ";
        if (p.name())
            os << '+' << *(p.name()) << ' ';

        const std::set<std::string>& ctx = a.contexts();
        for (std::set<std::string>::const_iterator i = ctx.begin();
             i != ctx.end(); ++i)
        {
            os << '@' << *i << ' ';
        }

        os << a.description();
        pid_ = l.front();
    }
    else
        os << "No items in the queue.";
    intf_.write(os.str());
}

